import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

/**
 * Reflection Examples
 * This class demonstrates the fundamental concepts of Java reflection mechanism
 */
public class Reflection {
    
    public static void main(String[] args) {
        Reflection example = new Reflection();
        
        // Demonstrating Class class
        example.demonstrateClassClass();
        
        // Demonstrating getting class information
        example.demonstrateGettingClassInfo();
        
        // Demonstrating dynamic object creation and method invocation
        example.demonstrateDynamicCreationAndInvocation();
        
        // Demonstrating practical use cases
        example.demonstratePracticalUseCases();
    }
    
    /**
     * Method to demonstrate Class class
     */
    public void demonstrateClassClass() {
        System.out.println("=== Class Class Examples ===");
        
        // Using getClass() method
        String str = "Hello";
        Class<?> class1 = str.getClass();
        System.out.println("Class from object: " + class1.getName());
        
        // Using class literal
        Class<?> class2 = String.class;
        System.out.println("Class from literal: " + class2.getName());
        
        // Using Class.forName() method
        try {
            Class<?> class3 = Class.forName("java.lang.String");
            System.out.println("Class from forName(): " + class3.getName());
        } catch (ClassNotFoundException e) {
            System.out.println("Class not found: " + e.getMessage());
        }
        
        // Checking if classes are the same
        System.out.println("class1 == class2: " + (class1 == class2));
        System.out.println();
    }
    
    /**
     * Method to demonstrate getting class information
     */
    public void demonstrateGettingClassInfo() {
        System.out.println("=== Getting Class Information ===");
        
        try {
            Class<?> clazz = Person.class;
            
            // Getting class name
            System.out.println("Class name: " + clazz.getName());
            System.out.println("Simple class name: " + clazz.getSimpleName());
            
            // Getting constructors
            Constructor<?>[] constructors = clazz.getConstructors();
            System.out.println("Number of public constructors: " + constructors.length);
            
            // Getting methods
            Method[] methods = clazz.getMethods();
            System.out.println("Number of public methods: " + methods.length);
            System.out.println("Method names:");
            for (Method method : methods) {
                if (method.getDeclaringClass() == Person.class) {
                    System.out.println("  " + method.getName());
                }
            }
            
            // Getting fields
            Field[] fields = clazz.getFields();
            System.out.println("Number of public fields: " + fields.length);
            
            Field[] declaredFields = clazz.getDeclaredFields();
            System.out.println("Number of declared fields: " + declaredFields.length);
            System.out.println("Field names:");
            for (Field field : declaredFields) {
                System.out.println("  " + field.getName() + " (" + field.getType().getSimpleName() + ")");
            }
            
        } catch (Exception e) {
            System.out.println("Error getting class information: " + e.getMessage());
        }
        
        System.out.println();
    }
    
    /**
     * Method to demonstrate dynamic object creation and method invocation
     */
    public void demonstrateDynamicCreationAndInvocation() {
        System.out.println("=== Dynamic Object Creation and Method Invocation ===");
        
        try {
            // Getting Class object
            Class<?> personClass = Class.forName("Person");
            
            // Creating object using default constructor
            Object personObj = personClass.getDeclaredConstructor().newInstance();
            System.out.println("Created object using default constructor: " + personObj);
            
            // Creating object using parameterized constructor
            Constructor<?> constructor = personClass.getConstructor(String.class, int.class);
            Object personObj2 = constructor.newInstance("Alice", 25);
            System.out.println("Created object using parameterized constructor: " + personObj2);
            
            // Invoking methods
            Method setNameMethod = personClass.getMethod("setName", String.class);
            setNameMethod.invoke(personObj, "Bob");
            
            Method getNameMethod = personClass.getMethod("getName");
            String name = (String) getNameMethod.invoke(personObj);
            System.out.println("Name set dynamically: " + name);
            
            // Accessing private fields
            Field nameField = personClass.getDeclaredField("name");
            nameField.setAccessible(true); // Allow access to private field
            nameField.set(personObj, "Charlie");
            String newName = (String) nameField.get(personObj);
            System.out.println("Private field 'name' accessed and modified: " + newName);
            
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | 
                 NoSuchMethodException | InvocationTargetException | NoSuchFieldException e) {
            System.out.println("Error in dynamic creation or invocation: " + e.getMessage());
            e.printStackTrace();
        }
        
        System.out.println();
    }
    
    /**
     * Method to demonstrate practical use cases
     */
    public void demonstratePracticalUseCases() {
        System.out.println("=== Practical Use Cases ===");
        
        // Object copying using reflection
        try {
            Person original = new Person("David", 30);
            Person copy = copyObject(original);
            System.out.println("Original: " + original);
            System.out.println("Copy: " + copy);
            
            // Object comparison using reflection
            Person another = new Person("David", 30);
            boolean areEqual = compareObjects(original, another);
            System.out.println("Objects are equal: " + areEqual);
            
        } catch (Exception e) {
            System.out.println("Error in practical use cases: " + e.getMessage());
        }
        
        System.out.println();
    }
    
    /**
     * Helper method to copy an object using reflection
     */
    public <T> T copyObject(T original) throws Exception {
        Class<?> clazz = original.getClass();
        Constructor<?> constructor = clazz.getConstructor();
        Object copy = constructor.newInstance();
        
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Object value = field.get(original);
            field.set(copy, value);
        }
        
        return (T) copy;
    }
    
    /**
     * Helper method to compare objects using reflection
     */
    public <T> boolean compareObjects(T obj1, T obj2) throws Exception {
        if (obj1 == null || obj2 == null) {
            return obj1 == obj2;
        }
        
        Class<?> clazz = obj1.getClass();
        if (clazz != obj2.getClass()) {
            return false;
        }
        
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Object value1 = field.get(obj1);
            Object value2 = field.get(obj2);
            if (value1 == null ? value2 != null : !value1.equals(value2)) {
                return false;
            }
        }
        
        return true;
    }
}

/**
 * Sample Person class for reflection demonstration
 */
class Person {
    private String name;
    private int age;
    public static final String SPECIES = "Homo sapiens";
    
    // Constructors
    public Person() {
        this.name = "Unknown";
        this.age = 0;
    }
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // Getters and setters
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
    
    // Other methods
    public void introduce() {
        System.out.println("Hi, I'm " + name + " and I'm " + age + " years old.");
    }
    
    public String getDetails() {
        return "Person[name=" + name + ", age=" + age + "]";
    }
    
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}